﻿using System;
using System.Collections.Generic;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning
{
	/// <summary>
	/// Probabilistic classifier with Bayesian update rule.
	/// </summary>
	[Serializable]
	public class ProbabilisticBayesianClassifier : ProbabilisticClassifier
	{
		protected Dictionary<int, int> classCounts = new Dictionary<int, int>();	// array for counting class entries.

		#region - Training. -
		/// <summary>
		/// 
		/// </summary>
		/// <param name="data"></param>
		/// <param name="clCount"></param>
		/// <param name="eps"></param>
		public override void Seed(List<TrainingSample> data, int clCount, float eps)
		{
			//
			// calculate all counters for each vector from training data.
			base.Seed(data, clCount, eps);

			//
			// calculate number of each class samples.
			foreach (var sample in data)
			{
				if (!classCounts.ContainsKey(sample.ClassID))
				{
					classCounts.Add(sample.ClassID, 1);
				}
				else
				{
					classCounts[sample.ClassID]++;
				}
			}

			//
			// calculate probabilities.
			RecalculatePosteriors();
		}

		/// <summary>
		/// Calculates posterior probabilities to detect class ID using each vector from [tData].
		/// </summary>
		public void RecalculatePosteriors()
		{
			// recalculate probabilities in the training data.
			// 0. calculate number of vectors which descript each class.
			var vectorClassCounts = MachineLearningElements.SumCounts(tData);
			var invVectorClassCounts = StructMath.Inverse(vectorClassCounts);

			// 1. calculate class probabilities.
			var classProbs = StructMath.ConvertToProbabilities(classCounts);
			foreach (var vp in tData)
			{
				// calculate conditional probabilities:
				//	p(ClassID | Vector)
				// where Vector -- given vector [vp]
				// ClassID -- ID of the class

				// calculate p(Vector | ClassID)
				vp.probs.Clear();
				foreach (var vpClassCount in vp.counts)
				{
					vp.probs.Add(vpClassCount.Key, vpClassCount.Value * invVectorClassCounts[vpClassCount.Key]);
				}

				// calculate factor (delimiter).
				var delim = 0f;
				foreach (var prob in vp.probs)
				{
					delim += prob.Value * classProbs[prob.Key];
				}
				delim = 1.0f / delim;	// delim^{-1}

				// calculate posterior probabilities for [vp] using Bayesian formula.
				foreach (var prob in classProbs)
				{
					if (vp.probs.ContainsKey(prob.Key))
					{
						vp.probs[prob.Key] = vp.probs[prob.Key] * prob.Value * delim;
					}
				}
			}	// foreach (var vp in tData)
		}

		/// <summary>
		/// Updates training data using given training sample and precision.
		/// </summary>
		/// <param name="sample">Training sample.</param>
		/// <param name="clCount">Overall number of classes.</param>
		/// <param name="eps">Precision parameter.</param>
		public override void Update(TrainingSample sample, int clCount, float eps)
		{
			var rows = MatrixMath.ConvertToRowsList(sample.Data);
			AddSeed(tData, rows, sample.ClassID, clCount, eps);

			//
			// recalculate class counters.
			if (classCounts.ContainsKey(sample.ClassID)) classCounts[sample.ClassID]++;
			else classCounts.Add(sample.ClassID, 1);

			//
			// update probabilities.
			RecalculatePosteriors();
		}

		///// <summary>
		///// Updates training data using Bayesian rule for the given training sample and precision.
		///// </summary>
		///// <param name="sample">Training sample.</param>
		///// <param name="clCount">Overall number of classes.</param>
		///// <param name="eps">Precision parameter.</param>
		//public override void Update(TrainingSample sample, int clCount, float eps)
		//{
		//    base.Update(sample, clCount, eps);	// update vectors counters.

		//    // update class info.
		//    if (classCounts.ContainsKey(sample.ClassID)) { classCounts[sample.ClassID]++; }
		//    else { classCounts.Add(sample.ClassID, 1); }

		//    // recalculate probabilities.
		//    // 1. calculate class probabilities.
		//    var classProbs = StructMath.ConvertToProbabilities(classCounts);
		//    foreach (var vp in tData)
		//    {
		//        vp.probs.Clear();

		//        // calculate conditional probabilities:
		//        //	p(Vector | ClassID)
		//        // where Vector -- given vector [vp]
		//        // ClassID -- ID of the class
		//        foreach (var vpClassCount in vp.counts)
		//        {
		//            vp.probs.Add(vpClassCount.Key, (float)vpClassCount.Value / classCounts[vpClassCount.Key]);
		//        }

		//        // calculate factor (delimiter).
		//        var delim = 0f;
		//        foreach (var prob in vp.probs)
		//        {
		//            delim += prob.Value * classProbs[prob.Key];
		//        }
		//        delim = 1.0f / delim;	// delim^{-1}

		//        // calculate posterior probabilities for [vp] using Bayesian formula.
		//        foreach (var key in vp.probs.Keys)
		//        {
		//            vp.probs[key] = vp.probs[key] * classProbs[key] * delim;
		//        }

		//        // validate posteriors: should give 1 in sum.
		//        var testSum = 0f;
		//        foreach (var value in vp.probs.Values)
		//        {
		//            testSum += value;
		//        }
		//        if (Math.Abs(testSum - 1f) < 0.001) throw new Exception(String.Format("Error in posterior probabilities calculation (sum = {0})", testSum));
		//    }	// foreach (var vp in tData)
		//} 
		#endregion

		#region - Recognition. -
		/// <summary>
		/// Performs recognition by the given object description.
		/// </summary>
		/// <param name="obj">Object description.</param>
		/// <param name="eps">Precision.</param>
		/// <returns>Class ID or '-1' if the class is unrecognized.</returns>
		public override int Recognize(float[,] obj, float eps)
		{
			var counts = GetClassProbabilities(obj, eps);
			return MachineLearningElements.GetMaxClassId(counts, -1);
		}
		#endregion
	}


	/// <summary>
	/// [molecule]
	/// 
	/// Class for committee of [ProbabilisticBayesianClassifier] objects.
	/// </summary>
	[Serializable]
	public class ProbabilisticBayesClassifierCommittee
	{
		private List<ProbabilisticBayesianClassifier> probc = new List<ProbabilisticBayesianClassifier>();

		/// <summary>
		/// List of algorithms.
		/// </summary>
		public List<ProbabilisticBayesianClassifier> Algorithms
		{
			get { return probc; }
		}

		/// <summary>
		/// Train [count] probabilistic classifiers.
		/// </summary>
		/// <param name="count">Number of classifiers to create.</param>
		/// <param name="data">Training data.</param>
		/// <param name="seedRate">Proportion of seed descriptions.</param>
		/// <param name="rand">RNG.</param>
		/// <param name="eps">Precision.</param>
		public void Train(int count, List<TrainingSample> data, float seedRate, Random rand, float eps)
		{
			probc.Clear();
			for (int i = 0; i < count; i++)
			{
				var tempC = new ProbabilisticBayesianClassifier();
				tempC.Train(data, seedRate, rand, eps);
				probc.Add(tempC);
			}
		}

		/// <summary>
		/// Recognize using averaging over trained classifiers.
		/// </summary>
		/// <param name="obj">Object description.</param>
		/// <param name="eps">Precision.</param>
		/// <returns>Class ID or -1 if no winning class is found.</returns>
		public int Recognize(float[,] obj, float eps)
		{
			if (probc.Count == 0) return -1;

			var res = new Dictionary<int, int>();
			foreach (var classifier in probc)
			{
				var temp = classifier.GetClassVotes(obj, eps);
				StructMath.Accumulate(ref res, temp);
			}

			return MachineLearningElements.GetMaxClassId(res, -1);
		}
	}
}
